package com.norriekoelle.virtualsensors.client.experiment;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.util.Log;


import com.norriekoelle.virtualsensors.client.BookDatabaseAdapter.Collection;
import com.norriekoelle.virtualsensors.shared.BookshelfObject;
import com.norriekoelle.virtualsensors.shared.LogToFile;
import com.norriekoelle.virtualsensors.shared.PairReceiver;
import com.norriekoelle.virtualsensors.shared.Receiver;

/**
 * Presenter for the experiment control flow.
 * 
 * @author Lauren Norrie (norrielm)
 */
public class Experiment implements Runnable {
	 
	private final String searchConditionInfo = "Locate each book using the categories " +
			"and double tap to select.";
	private final String categoriseConditionInfo = "Locate each book using the categories " +
			"and double tap to select. Categorise by longpressing on the book.";
	private final String kinectSystemInfo = "The post-it notes on the bookshelf may be used to " +
			"filter between categories.";
	private final String phoneSystemInfo = "The menu on the phone may be used to filter between " +
			"categories.";
	private final String startInfo = "Welcome to the experiment. You will be guided through a" 
		+ " series of tasks as explained on the information sheet.";
	private final String endOfConditionInfo = "End of condition. Please take a break and continue" 
		+ " when you are ready.";
	private final String endInfo = "Experiment complete. Thanks for taking part! Please participate"
		+ " in a short interview.";
	
	// List of sets, each with a list of <book, collection> (if relevant)
//	private ArrayList<Pairs<Integer, Integer>> generated_sets = new ArrayList<Pairs<Integer, Integer>>(4);
	// TODO check indices
	private int[][] sets = {
			new int[]{35,    34,    13,    23,   40,    1,    19,   42,    18,    11},
			new int[]{19, 2, 22, 1, 31, 1, 0, 3, 10, 2, 5, 2, 9, 1, 28, 1, 33, 2, 29, 1},
			new int[]{15,    41,    6,     26,   16,    32,   44,    2,    27,    8},
			new int[]{43, 1, 14, 2, 39, 2, 7, 2, 30, 2, 12,2, 37, 2, 21, 1, 36, 3, 4, 1}
	};
	
//	private class Pairs<A, B> {
//		private ArrayList<Pair<A, B>> pairs = new ArrayList<Pair<A, B>>(8);
//		
//		public ArrayList<Pair<A,B>> getPairs() {
//			return pairs;
//		}
//	}
//	
//	private class Pair<A, B> {
//		private A a;
//		private B b;
//		
//		private Pair (A a, B b) {
//			this.a = a;
//			this.b = b;
//		}
//		
//		private Pair (A a) {
//			this.a = a;
//			this.b = null;
//		}
//		
//		public A getA() {
//			return a;
//		}
//		
//		public B getB() {
//			return b;
//		}
//		
//		public String toString() {
//			return (b == null) ? a.toString() : a.toString() + " " + b.toString();
//		}
//	}
	
	public enum SystemType {
		KINECT("Post-its"), 
		PHONE_ONLY("Phone menu");
		
		private String name;
		
		private SystemType(String name) {
			this.name = name;
		}
		
		public String getName() {
			return name;
		}
	}
	protected enum Condition {
		SEARCH("Find books"), 
		CATEGORISE("Categorise books");
		
		private String name;
		
		private Condition(String name) {
			this.name = name;
		}
		
		public String getName() {
			return name;
		}	
	}
	private Context context;
	private SystemType firstSystem;
	private SystemType currentSystem;
	private ArrayList<BookshelfObject> untestedObjects;
	private Runnable doNextTask;
	private Runnable nextSystemCallback;
	private Receiver<BookshelfObject> doSearchTaskCallback;
	protected PairReceiver<BookshelfObject, Collection> doCategoriseTaskCallback;
	private Runnable endOfTaskCallback;
	private Timer t = new Timer();
	private boolean timeUp;
	private int maxTaskLength;
	private LogToFile log;
	// index into the set 
	int category = 0, task = 0;
	private TaskDialog dialog;
	private String LOG = "LOG";
	private Receiver<String> logServerCallback;
	
	public Experiment(Context c, Receiver<BookshelfObject> doSearchTaskCallback, 
			PairReceiver<BookshelfObject, Collection> doCategoriseTaskCallback, Runnable endOfTaskCallback, 
			Runnable nextSystemCallback, Receiver<String> logServerCallback, ArrayList<BookshelfObject> allObjects, SystemType firstSystem, LogToFile log) {
		this.log = log;
		this.context = c;
		this.doSearchTaskCallback = doSearchTaskCallback;
		this.doCategoriseTaskCallback = doCategoriseTaskCallback;
		this.endOfTaskCallback = endOfTaskCallback;
		this.nextSystemCallback = nextSystemCallback;
		this.logServerCallback = logServerCallback;
		this.untestedObjects = allObjects;
		this.firstSystem = firstSystem;
		this.maxTaskLength = 10;//allObjects.size()/4;
	}
	
	/**
	 * Start the experiment for a given system.
	 */
	public void startExperiment(SystemType system) {
		log.writeWTime(String.format("Starting experiment with the %s", system.toString()));
		Log.d(LOG, String.format("Starting experiment with the %s", system.toString()));
		currentSystem = system;
		dialog = new TaskDialog(context, system.getName(), 
				(system.equals(SystemType.KINECT) ? kinectSystemInfo : phoneSystemInfo));
		dialog.setPositiveButton("Next", 
			new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int id) {
					dialog.dismiss();
					startCondition(Condition.SEARCH);
				}
			}
		);
	    dialog.show();
	}

	public void startTaskTimer(final SystemType system, final Condition condition) {
		// Lets fix the conditions to 3 minutes.
		int seconds = 3 * 60;
		t = new Timer();
		timeUp = false;
		t.schedule(new TimerTask() {
			@Override public void run() {
				timeUp = true;
				endOfTaskCallback.run();
			}
		}, seconds * 1000);
	}
		
	public void startCondition(final Condition condition) {
//		generated_sets.add(new Pairs<Integer, Integer>());
		log.writeWTime(String.format("Starting %s tasks", condition.getName()));
		Log.d(LOG, String.format("Starting %s tasks", condition.getName()));
		logServerCallback.accept(String.format("%s task in %s", currentSystem.getName(), condition.getName()));
		String title = (condition.equals(Condition.CATEGORISE) ? "Find and categorise 10 books." : "Find 10 books.");
		dialog = new TaskDialog(context, title, 
				(condition.equals(Condition.CATEGORISE) ? categoriseConditionInfo : searchConditionInfo));
		dialog.setPositiveButton("Start", 
			new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int id) {
					dialog.dismiss();
					runTasks(currentSystem, condition, 0, maxTaskLength);
				}
			}
		);
	    dialog.show();
	}
	
	// TODO(norrielm): Give an overview of time/no books found.
	public void endOfCondition(final SystemType system, final Condition condition) {
		category++;
		task = 0;
		// Show a condition TLX feedback dialog?
		if (!condition.equals(Condition.CATEGORISE)) {
			dialog = new TaskDialog(context, "Condition complete", endOfConditionInfo);
			dialog.setPositiveButton("Next", 
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int id) {
						dialog.dismiss();
						startCondition(Condition.CATEGORISE);
					}
				}
			);
		    dialog.show();
		} else if (firstSystem.equals(system)) {
			nextSystemCallback.run();
			dialog = new TaskDialog(context, system.getName() + " complete", endOfConditionInfo);
			dialog.setPositiveButton("Next", 
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int id) {
						dialog.dismiss();
						startExperiment(firstSystem.equals(SystemType.KINECT) ? SystemType.PHONE_ONLY : 
							SystemType.KINECT);
					}
				}
			);
		    dialog.show();
		} else {
			// Show an experiment complete TLX dialog.
			dialog = new TaskDialog(context, "Experiment complete", endInfo);
			dialog.setPositiveButton("Done", 
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int id) {
						dialog.dismiss();
//						for (Pairs<Integer, Integer> set : generated_sets) {
//							String s = "Set " + set + ": ";
//							for (Pair<Integer, Integer> book : set.getPairs()) {
//								s += book + ", ";
//							}
//							Log.d("LOG", s);
//						}
					}
				}
			);
		    dialog.show();
		    log.writeWTime("------FINISH----------");
		    Log.d(LOG, "------FINISH----------");
		}
	}
	
	private Collection categoryToMove = null;
	private Drawable cover;
	public void runTasks(final SystemType system, final Condition condition, 
			final int taskNumber, final int totalTasks) {	
		if (taskNumber >= totalTasks) {
			endOfCondition(system, condition);
			return;
		}
		// Choose a book that has not been tested, reveal the category, picture and name.
//		int randomBookIndex = (int)(Math.random() * (untestedObjects.size() - 1));
//		final BookshelfObject bookToFind = untestedObjects.remove(randomBookIndex);
		int randomBookIndex = sets[category][task++];
		final BookshelfObject bookToFind = untestedObjects.get(randomBookIndex);
		String title = String.format("%s | Task %d/%d", 
				bookToFind.getBookCategory().getName(), (taskNumber + 1), totalTasks);
		String message = String.format("Find %s in %s", bookToFind.getBookName(), 
				bookToFind.getBookCategory().getName());
		if (condition.equals(Condition.CATEGORISE)) {
//			int randomCategoryIndex = (int)(Math.random() * 2) + 1; // Add 1 to ignore index 0: ALL.
			int randomCategoryIndex = sets[category][task++];
			if (Collection.values()[randomCategoryIndex].equals(bookToFind.getBookCategory())) {
				randomCategoryIndex = (randomCategoryIndex == 3 ? 1 : randomCategoryIndex + 1);
			}
			categoryToMove = Collection.values()[randomCategoryIndex];
			message = String.format("Move %s in %s to %s", bookToFind.getBookName(), 
					bookToFind.getBookCategory().getName(), categoryToMove.getName());
//			Pairs<Integer, Integer> s = generated_sets.get(category);
//			ArrayList<Pair<Integer, Integer>> c = s.getPairs();
//			c.add(taskNumber, new Pair<Integer, Integer>(Integer.valueOf(bookToFind.getDatabaseId()), categoryToMove.ordinal()));
//		} else {
//			Pairs<Integer, Integer> s = generated_sets.get(category);
//			ArrayList<Pair<Integer, Integer>> c = s.getPairs();
//			c.add(taskNumber, new Pair<Integer, Integer>(Integer.valueOf(bookToFind.getDatabaseId())));
		} else {
			categoryToMove = null;
		}
		message = ((taskNumber == 0) ? "" : ((timeUp) ? "Time up! " : "Correct! ")) + message;
		
		dialog = new TaskDialog(context, title, message);
		dialog.setPositiveButton("Start", 
			new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int id) {
					dialog.dismiss();
					doNextTask = new Runnable() {
						@Override public void run() {
							runTasks(system, condition, taskNumber + 1, totalTasks);
						}
					};
					if (categoryToMove == null) {
						doSearchTaskCallback.accept(bookToFind);
						log.writeWTime("Task " + (taskNumber + 1) + " Find " + bookToFind.toString());
						Log.d(LOG, "Task " + (taskNumber + 1) + " Find " + bookToFind.toString());
					} else {
						doCategoriseTaskCallback.accept(bookToFind, categoryToMove);
						log.writeWTime("Task " + (taskNumber + 1) + " Move " + bookToFind.toString() + " to " + categoryToMove.getName());
						Log.d(LOG, "Task " + (taskNumber + 1) + " Move " + bookToFind.toString() + " to " + categoryToMove.getName());
					}
					startTaskTimer(currentSystem, condition);
				}
			}
		);
		// TODO(norrielm): Find out how to load drawable from URL
		try {
			cover = Drawable.createFromStream((InputStream) 
				new URL(bookToFind.getDrawableResourceId()).getContent(), bookToFind.getDatabaseId());
			dialog.setIcon(cover);
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	    dialog.show();
//		runTasks(system, condition, taskNumber + 1, totalTasks);
	}
	
	public void doNextTask() {
		// Stop timer.
		t.cancel();
		log.writeWTime(timeUp ? "Time up" : "Task successful");
		Log.d("LOG" , timeUp ? "Time up" : "Task successful");
		doNextTask.run();
	}

	/**
	 * Dialog to inform the user during their tasks.
	 */
	public class TaskDialog extends AlertDialog.Builder {
		
		public TaskDialog(Context context, String title, String message) {
			super(context);
			this.setTitle(title)
			.setMessage(message)
			.setCancelable(false);
		}
	}

	@Override
	public void run() {
		log.writeWTime("-------START---------");
		Log.d("LOG", "-------START---------");
		dialog = new TaskDialog(context, "Welcome!", startInfo);
		dialog.setPositiveButton("Next", 
			new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int id) {
					dialog.dismiss();
					startExperiment(firstSystem);
				}
			}
		);
	    dialog.show();
	}
}
